/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003-2005 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.util.BitSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;
import edu.umd.cs.findbugs.ba.type.TypeFrame;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.Global;

public class PruneUnconditionalExceptionThrowerEdges implements EdgeTypes {
	private static final boolean DEBUG = SystemProperties.getBoolean("cfg.prune.throwers.debug");
	private static final boolean DEBUG_DIFFERENCES = SystemProperties.getBoolean("cfg.prune.throwers.differences.debug");
	private static final String UNCONDITIONAL_THROWER_METHOD_NAMES = SystemProperties.getProperty("findbugs.unconditionalThrower", " ").replace(',', '|');

	private MethodGen methodGen;
	private CFG cfg;
	private ConstantPoolGen cpg;
	private TypeDataflow typeDataflow;
	private AnalysisContext analysisContext;
	private boolean cfgModified;

	private static final Pattern unconditionalThrowerPattern;
	private  static final BitSet RETURN_OPCODE_SET = new BitSet();
	static {
		RETURN_OPCODE_SET.set(Constants.ARETURN);
		RETURN_OPCODE_SET.set(Constants.IRETURN);
		RETURN_OPCODE_SET.set(Constants.LRETURN);
		RETURN_OPCODE_SET.set(Constants.DRETURN);
		RETURN_OPCODE_SET.set(Constants.FRETURN);
		RETURN_OPCODE_SET.set(Constants.RETURN);
		Pattern p;
		try {
			p =  Pattern.compile(UNCONDITIONAL_THROWER_METHOD_NAMES);
			if (DEBUG) {
				System.out.println("Pattern is '" + p +"'");
				System.out.println(p.matcher("showInvalidPage").matches());
			}
		} catch (RuntimeException e) {
			AnalysisContext.logError("Error compiling unconditional thrower pattern " + UNCONDITIONAL_THROWER_METHOD_NAMES, e);
			p = Pattern.compile(" ");
		}
		unconditionalThrowerPattern = p;
	}

	public PruneUnconditionalExceptionThrowerEdges(/*ClassContext classContext,*/ JavaClass javaClass, Method method,
			MethodGen methodGen, CFG cfg, ConstantPoolGen cpg, TypeDataflow typeDataflow, AnalysisContext analysisContext) {
//		this.classContext = classContext;
		this.methodGen = methodGen;
		this.cfg = cfg;
		this.cpg = cpg;
		this.typeDataflow = typeDataflow;
		this.analysisContext = analysisContext;
	}

	public void execute() throws  DataflowAnalysisException {
		AnalysisContext currentAnalysisContext = AnalysisContext.currentAnalysisContext();
		if (currentAnalysisContext.getBoolProperty(AnalysisFeatures.CONSERVE_SPACE))
			throw new IllegalStateException("This should not happen");
		boolean foundInexact = false;
		Set<Edge> deletedEdgeSet = new HashSet<Edge>();
//		TypeDataflow typeDataflow = classContext.getTypeDataflow(method);

		if (DEBUG)
			System.out.println("PruneUnconditionalExceptionThrowerEdges: examining " +
					SignatureConverter.convertMethodSignature(methodGen));

		for (Iterator<BasicBlock> i = cfg.blockIterator(); i.hasNext();) {
			BasicBlock basicBlock = i.next();
			if (!basicBlock.isExceptionThrower())
				continue;

			InstructionHandle instructionHandle = basicBlock.getExceptionThrower();
			Instruction exceptionThrower = instructionHandle.getInstruction();
			if (!(exceptionThrower instanceof InvokeInstruction))
				continue;

			InvokeInstruction inv = (InvokeInstruction) exceptionThrower;
			boolean foundThrower = false;
			boolean foundNonThrower = false;
			boolean isExact = true;
			XMethod primaryXMethod = XFactory.createXMethod(inv, cpg);
			final String methodName = primaryXMethod.getName();
			final boolean matches = unconditionalThrowerPattern.matcher(methodName).matches();
			if (DEBUG) 
				System.out.println("Checking '" + methodName + "' is " + matches );

			if (matches) {
				if (DEBUG) System.out.println("\tmatched for " + instructionHandle + " : " + primaryXMethod);

				foundThrower = true; 
			} else if (inv instanceof INVOKEINTERFACE) {
				continue;
			} else if (inv instanceof INVOKESTATIC) {
				foundThrower = isUnconditionalThrower(primaryXMethod);
			} else {
				String className = inv.getClassName(cpg);
				if (DEBUG) System.out.println("\tlooking up method for " + instructionHandle+ " : " + primaryXMethod);

				Location loc = new Location(instructionHandle, basicBlock);
				TypeFrame typeFrame = typeDataflow.getFactAtLocation(loc);
				// if (primaryXMethod.isAbstract()) continue;
				Set<XMethod> targetSet = null;
				try {

					if (className.startsWith("["))
						continue;
					String methodSig = inv.getSignature(cpg);
					if (!methodSig.endsWith("V") && !methodSig.endsWith("Exception;") && !methodSig.endsWith("Error;"))
						continue;

					targetSet = Hierarchy2.resolveMethodCallTargets(inv, typeFrame, cpg);

					for(XMethod xMethod : targetSet) {
						if (DEBUG) System.out.println("\tFound " + xMethod);

						// Ignore abstract and native methods
						if (!( xMethod.isFinal() || xMethod.isStatic() || xMethod.isPrivate() )) try {
							isExact = false;
							XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, xMethod.getClassDescriptor());
							if (xClass.isAbstract()) continue;
						} catch (CheckedAnalysisException e) {
							AnalysisContext.logError("Unable to resolve class for " + xMethod, e);
						}
						boolean isUnconditionalThrower = isUnconditionalThrower(xMethod);
						if (isUnconditionalThrower) {
							foundThrower = true;
							if (DEBUG) System.out.println("Found thrower");
						}
						else {
							foundNonThrower = true;
							if (DEBUG) System.out.println("Found non thrower");
						}

					}
				} catch (ClassNotFoundException e) {
					analysisContext.getLookupFailureCallback().reportMissingClass(e);
				}
			}
			boolean newResult = foundThrower && !foundNonThrower;
			if (newResult) {
				if (!isExact)
					foundInexact = true;
				// Method always throws an unhandled exception
				// Remove the normal control flow edge from the CFG.
				Edge fallThrough = cfg.getOutgoingEdgeWithType(basicBlock,
						FALL_THROUGH_EDGE);
				if (fallThrough != null) {
					if (DEBUG) {
						System.out.println("\tREMOVING normal return for: " + primaryXMethod);
					}
					deletedEdgeSet.add(fallThrough);
				}
			}

		}

		if (!deletedEdgeSet.isEmpty()) {
			cfgModified = true;
			if (foundInexact)
				cfg.setFlag(CFG.FOUND_INEXACT_UNCONDITIONAL_THROWERS);
		// Remove all edges marked for deletion
		for (Edge edge : deletedEdgeSet) {
			cfg.removeEdge(edge);

		}
		}
	}

	/**
     * @param xMethod
     * @return
     */
    private boolean isUnconditionalThrower(XMethod xMethod) {
	    return xMethod.isUnconditionalThrower() && !xMethod.isUnsupported() && !xMethod.isSynthetic();
    }

	/**
     * @param xMethod
     * @param javaClass
     * @param method
     * @return true if method unconditionally throws
     * @deprecated Use {@link #doesMethodUnconditionallyThrowException(XMethod)} instead
     */
	@Deprecated
    static public  Boolean doesMethodUnconditionallyThrowException(XMethod xMethod, JavaClass javaClass, Method method) {
        return doesMethodUnconditionallyThrowException(xMethod);
    }

	/**
	 * @param xMethod
	 * @return true if method unconditionally throws
	 */
	static public  boolean doesMethodUnconditionallyThrowException(XMethod xMethod) {
		return xMethod.isUnconditionalThrower();
	}

	/**
	 * Return whether or not the CFG was modified.
	 * 
	 * @return true if CFG was modified, false otherwise
	 */
	public boolean wasCFGModified() {
		return cfgModified;
	}
}

//vim:ts=4
